unit GLGUISkinManager;

interface

{$I GLScene.inc}

uses
  SysUtils, StrUtils, Classes, Dialogs,
  VectorGeometry, GLTexture, GLGraphics, GLTextureFormat, GLColor,
  GLSGenerics,
  GLSCrossXML,
  GLS_Material,
  GLGUISkin,
  GLGUIState,
  GLGUIKeys;

type
  TGLGUITexture = class
  public
    Image: TGLTextureImageEx;
    Width: Integer;
    Height: Integer;
  end;

  TGLGUISkinsClassesList = GList < TGLGUISkinClass >;
  TGLTexturesList = GList < TGLGUITexture >;

  TGLGUISkinManager = class
  private
    FSkins: TGLGUICustomSkinsList;
    FTextures: TGLTexturesList;
    FClasses: TGLGUISkinsClassesList;
    FParseSkinsOnLoad: Boolean;
    FDefaultSampler: TGLTextureSampler;
    procedure AddClass(ASkinClass: TGLGUISkinClass);
    function FindClass(AClassName: String): TGLGUISkinClass;
    procedure LoadSkinTexture(const ANode: GLSXMLNode);
    function FindTexture(const AName: String): TGLGUITexture;
    function ParseSkin(const ANode: GLSXMLNode): TGLGUICustomSkin;
    procedure ParseKeys(const ANode: GLSXMLNode; const ASkin: TGLGUICustomSkin);
    function GetSkinIndex(ASkinName: String): Integer;
    function GetDefaultSampler: TGLTextureSampler;
  public
    constructor Create;
    procedure LoadSkinFromFile(const AFileName: String);
    function SkinExist(const ASkinName: String) : Boolean;
    function GetSkinByName(const ASkinName: String): TGLGUICustomSkin;
    function GetSkinByIndex(const AIndex: Integer): TGLGUICustomSkin;
    destructor Destroy; override;
    property Skin[const ASkinName: String]: TGLGUICustomSkin read GetSkinByName;
    property DefaultSampler: TGLTextureSampler read GetDefaultSampler;
  end;

function GUISkinManager: TGLGUISkinManager;

procedure RegisterSkin(ASkin: TGLGUISkinClass);

implementation

uses
  GLGUIUtils;

type
  TGLGUICustomSkinAccessProvider = class(TGLGUICustomSkin);
  TGLGUICustomStateDescriptorAccessProvider = class(TGLGUICustomStateDescriptor);

{$IFDEF GLS_COMPILER_2005_UP}{$REGION 'Singleton'}{$ENDIF}

var
  vSkinManager: TGLGUISkinManager;

function GUISkinManager: TGLGUISkinManager;
begin
  if vSkinManager = nil then
    vSkinManager := TGLGUISkinManager.Create;
  Result := vSkinManager;
end;

{$IFDEF GLS_COMPILER_2005_UP}{$ENDREGION}{$ENDIF}

procedure RegisterSkin(ASkin: TGLGUISkinClass);
begin
  GUISkinManager.AddClass(ASkin);
end;

constructor TGLGUISkinManager.Create;
const
  cDefaultTextureSamplerName = 'DefaultTextureSampler';
begin
  inherited;
  FClasses :=  TGLGUISkinsClassesList.Create;
  FSkins := TGLGUICustomSkinsList.Create;
  FTextures := TGLTexturesList.Create;
  FParseSkinsOnLoad := True;
  with GetInternalMaterialLibrary do
  begin
    FDefaultSampler := Components.GetSamplerByName(cDefaultTextureSamplerName);
    if not Assigned(FDefaultSampler) then
      FDefaultSampler := AddSampler(cDefaultTextureSamplerName);
  end;
  FDefaultSampler.MagFilter := maLinear;
  FDefaultSampler.MinFilter := miLinear;
end;

procedure TGLGUISkinManager.AddClass(ASkinClass: TGLGUISkinClass);
begin
  FClasses.Add(ASkinClass);
end;

function TGLGUISkinManager.FindClass(AClassName: String): TGLGUISkinClass;
var
  I: Integer;
begin
  Result := TGLGUICustomSkin;
  for I := 0 to FClasses.Count - 1 do
  begin
    if SameStr(FClasses.Items[i].ClassName, AClassName) then
    begin
      Result := FClasses.Items[i];
      Exit;
    end;
  end;
end;

procedure TGLGUISkinManager.LoadSkinFromFile(const AFileName: String);
var
  SkinsXML: GLSXMLDocument;
  FirstNode: GLSXMLNode;
  SkinsNode: GLSXMLNode;
  TexturesNode: GLSXMLNode;
  CurrentTexture: GLSXMLNode;
  CurrentSkin: GLSXMLNode;
  TemporarySkin: TGLGUICustomSkin;
  i: Integer;
begin
  if FileExists(AFileName) then
  begin
    SkinsXML := GLSNewXMLDocument();
    SkinsXML.LoadFromFile(AFileName);
    FirstNode := SkinsXML.DocumentElement;
    if not FindXMLNode(FirstNode, 'Skins', SkinsNode) then
      raise Exception.Create('Skins node in "' + AFileName + '" not exist');
    if not FindXMLNode(FirstNode, 'Textures', TexturesNode) then
      raise Exception.Create('Textures node in "' + AFileName + '" not exist');
  end
	else
  	raise Exception.Create('Skin file "' + AFileName + '" not exist');

  for i := 0 to TexturesNode.ChildNodes.Count - 1 do
  begin
    CurrentTexture := TexturesNode.ChildNodes.Get(i);
    LoadSkinTexture(CurrentTexture);
  end;

  for i := 0 to SkinsNode.ChildNodes.Count - 1 do
  begin
    CurrentSkin := SkinsNode.ChildNodes.Get(i);
    TemporarySkin := ParseSkin(CurrentSkin);

    if not (TemporarySkin = nil) then
      FSkins.Add(TemporarySkin);
  end;

  if FParseSkinsOnLoad then
    for i := 0 to FSkins.Count - 1 do
      FSkins.Items[i].Parse;
end;

function TGLGUISkinManager.ParseSkin(const ANode: GLSXMLNode): TGLGUICustomSkin;
var
  vSkinName, vSkinTexture: String;
  vSkinType: String;
  vTextureData: TGLGUITexture;
begin
  GetXMLAttribute(ANode, 'Type', vSkinType);
  with ANode do
  begin
    Result := TGLGUISkinClass(FindClass(vSkinType)).Create;
    GetXMLAttribute(ANode, 'Name', vSkinName);
    Result.Name := vSkinName;
    Result.SkinType := vSkinType;
    GetXMLAttribute(ANode, 'Texture', vSkinTexture);
    vTextureData := FindTexture(vSkinTexture);
    if Assigned(vTextureData) then
    begin
      Result.Texture := vTextureData.Image;
      with TGLGUICustomSkinAccessProvider(Result) do
      begin
        FTextureWidth := vTextureData.Width;
        FTextureHeight := vTextureData.Height;
      end;
    end;
    ParseKeys(ANode, Result);
    TGLGUICustomSkinAccessProvider(Result).FXMLNode := ANode;
  end;
end;

procedure TGLGUISkinManager.ParseKeys(const ANode: GLSXMLNode; const ASkin: TGLGUICustomSkin);
var
  vKeysNode: GLSXMLNode;
  vCurrentKey: TGLGUIKey;
  vCurrentKeyXML: GLSXMLNode;
  vCurrentString: String;
  i: Integer;
begin
  if FindXMLNode(ANode,'Keys',vKeysNode) then
  begin
    for i := 0 to vKeysNode.ChildNodes.Count - 1 do
    begin
      vCurrentKeyXML := vKeysNode.ChildNodes.Get(i);
      GetXMLAttribute(vCurrentKeyXML, 'Type', vCurrentString);
      vCurrentKey := TGLGUIKey(StringToKeyClass(vCurrentString).Create);
      vCurrentKey.DecimalSeparator := '.';
      GetXMLAttribute(vCurrentKeyXML, 'Name', vCurrentString);
      vCurrentKey.Name := vCurrentString;
      GetXMLAttribute(vCurrentKeyXML, 'Value', vCurrentString);
      vCurrentKey.SetStringValue(vCurrentString);
      ASkin.AddKey(vCurrentKey);
    end;
  end;
end;

procedure TGLGUISkinManager.LoadSkinTexture(const ANode: GLSXMLNode);
var
  i: Integer;
  vTexture: TGLTextureImageEx;
  vTextureData: TGLGUITexture;
  vCurrentElementXML: GLSXMLNode;
  vCurrentString: String;
begin
  GetXMLAttribute(ANode, 'Name', vCurrentString);
  with GetInternalMaterialLibrary do
    vTexture := AddTexture(vCurrentString);
  if not Assigned(vTexture) then
  begin
    vTexture := GetInternalMaterialLibrary.AddTexture(vCurrentString);
    vTexture.InternalFormat := tfRGBA8;
    vTexture.ApplicationResource := False;
  end;
  vTextureData := TGLGUITexture.Create;
  vTextureData.Image := vTexture;
  for i := 0 to ANode.ChildNodes.Count - 1 do
  begin
    vCurrentElementXML := ANode.ChildNodes.Get(i);
    GetXMLAttribute(vCurrentElementXML, 'Type', vCurrentString);
    if SameStr(vCurrentString, 'Path') then
    begin
      GetXMLAttribute(vCurrentElementXML, 'Value', vCurrentString);
      vTexture.SourceFile := vCurrentString;
    end
    else if SameStr(vCurrentString, 'Width') then
    begin
      GetXMLAttribute(vCurrentElementXML, 'Value', vCurrentString);
      vTextureData.Width := StrToInt(vCurrentString);
    end
    else if SameStr(vCurrentString, 'Height') then
    begin
      GetXMLAttribute(vCurrentElementXML, 'Value', vCurrentString);
      vTextureData.Height := StrToInt(vCurrentString);
    end;
  end;
  FTextures.Add(vTextureData);
end;

function TGLGUISkinManager.FindTexture(const AName: String): TGLGUITexture;
var
  i: Integer;
begin
  Result := nil;
  for i := 0 to FTextures.Count - 1 do
    if FTextures[i].Image.Name = AName then
    begin
      Result := FTextures[i];
      Exit;
    end;
end;

function TGLGUISkinManager.SkinExist(const ASkinName: String) : Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to FSkins.Count - 1 do
    if SameStr(FSkins[i].Name, ASkinName) then
    begin
      Result := True;
      Exit;
    end;
end;

function TGLGUISkinManager.GetSkinByName(const ASkinName: String) : TGLGUICustomSkin;
var
  i: Integer;
begin
  Result := nil;

  for i := 0 to FSkins.Count - 1 do
    if SameStr(FSkins[i].Name, ASkinName) then
    begin
      Result := FSkins[i];
      Exit;
    end;

  if Result = nil then
    raise Exception.Create('Skin ' + ASkinName + ' was not loaded, please check name');
end;

function TGLGUISkinManager.GetSkinByIndex(const AIndex: Integer): TGLGUICustomSkin;
begin
  if AIndex < FSkins.Count then
    Result := FSkins.Items[AIndex]
  else
    raise Exception.Create('Skin was not loaded, please check index');
end;

function TGLGUISkinManager.GetSkinIndex(ASkinName: String): Integer;
var
  i: Integer;
begin
  Result := -1;

  for i := 0 to FSkins.Count - 1 do
    if SameStr(FSkins[i].Name, ASkinName) then
    begin
      Result := i;
      Exit;
    end;

  if Result = -1 then
    raise Exception.Create('Skin ' + ASkinName + ' was not loaded, please check name');
end;

function TGLGUISkinManager.GetDefaultSampler: TGLTextureSampler;
begin
  Result := FDefaultSampler;
end;

destructor TGLGUISkinManager.Destroy;
var
  i: Integer;
begin
  for i := 0 to FSkins.Count - 1 do
//    (FSkins[i] as FSkins[i].ClassType).Free;
    FSkins[i].Free;
  FSkins.Free;
  for i := 0 to FTextures.Count - 1 do
    FTextures[i].Free;
  FTextures.Free;
  FClasses.Free;
  inherited;
end;

initialization

finalization
  FreeAndNil(vSkinManager);

end.
